package cn.iocoder.yudao.module.member.user.service;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.PhoneUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.business.basic.dao.query.CoreLambdaQueryWrapper;
import cn.iocoder.yudao.framework.business.basic.dao.repository.SerialNoRedisRepository;
import cn.iocoder.yudao.framework.business.basic.pojo.vo.UserSimpleVO;
import cn.iocoder.yudao.framework.common.biz.config.ConfigCommonApi;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.enums.TerminalEnum;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.exception.KnownServiceException;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.region.RegionUtils;
import cn.iocoder.yudao.framework.region.enums.RegionDataSet;
import cn.iocoder.yudao.framework.util.TransactionUtils;
import cn.iocoder.yudao.module.api.infrastructure.ConfigConstants;
import cn.iocoder.yudao.module.api.infrastructure.sms.SmsCodeApi;
import cn.iocoder.yudao.module.api.infrastructure.sms.dto.code.SmsCodeUseReqDTO;
import cn.iocoder.yudao.module.api.infrastructure.sms.enums.SmsSceneEnum;
import cn.iocoder.yudao.module.api.infrastructure.social.SocialClientApi;
import cn.iocoder.yudao.module.api.infrastructure.social.SocialUserApiV2;
import cn.iocoder.yudao.module.api.infrastructure.social.dto.SocialWxPhoneNumberInfoRespDTO;
import cn.iocoder.yudao.module.member.user.convert.AuthConvert;
import cn.iocoder.yudao.module.member.user.dal.dataobject.MemberUserDO;
import cn.iocoder.yudao.module.member.user.dal.mysql.MemberUserMapper;
import cn.iocoder.yudao.module.member.user.mq.MemberUserProducer;
import cn.iocoder.yudao.module.member.user.vo.*;
import com.google.common.annotations.VisibleForTesting;
import jakarta.annotation.Nullable;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
import static cn.iocoder.yudao.module.api.member.ErrorCodeConstants.*;

/**
 * 用户 User Service 实现类
 *
 * @author 山野羡民
 */
@Service
@Valid
@Slf4j
public class MemberUserServiceImpl implements MemberUserService {
    private static final String MEMBER_USER_INFO = "xianmin:member:user_info";

    @Resource
    private MemberUserMapper memberUserMapper;

    @Resource
    @Lazy // 延迟，避免循环依赖报错
    private SmsCodeApi smsCodeApi;
    @Resource
    @Lazy // 延迟，避免循环依赖报错
    private SocialClientApi socialClientApi;
    @Resource
    @Lazy // 延迟，避免循环依赖报错
    private SocialUserApiV2 socialUserApiV2;
    @Resource
    @Lazy // 延迟，避免循环依赖报错
    private ConfigCommonApi configApi;

    @Resource
    private PasswordEncoder passwordEncoder;

    @Resource
    private MemberUserProducer memberUserProducer;

    @Resource
    private SerialNoRedisRepository serialNoRedisRepository;

    @Override
    public Long getInviterId(Long id) {
        return memberUserMapper.selectPid(id);
    }

    @Override
    public Set<Long> getInviteeIds(Long id) {
        return memberUserMapper.selectIdsByPid(id);
    }

    @Override
    public MemberUserDO getUserByNo(String no) {
        return memberUserMapper.selectByNo(no);
    }

    @Override
    public MemberUserDO getUserByUsername(String username) {
        return memberUserMapper.selectByUsername(username);
    }

    @Override
    public MemberUserDO getUserByMobile(String mobile) {
        return memberUserMapper.selectByMobile(mobile);
    }

    @Override
    public List<MemberUserDO> getUserListByMobile(String mobile) {
        return memberUserMapper.selectListByMobileLike(mobile);
    }

    @Override
    public List<MemberUserDO> getUserListByNickname(String nickname) {
        return memberUserMapper.selectListByNicknameLike(nickname);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public MemberUserDO createUserIfAbsent(String mobile, String registerIp, Integer terminal) {
        // 用户已经存在
        MemberUserDO user = memberUserMapper.selectByMobile(mobile);
        if (user != null) {
            return user;
        }
        // 用户不存在，则进行创建
        return createUser(mobile, null, null, null, registerIp, terminal, false);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public MemberUserDO createUser(String nickname, String avtar, String registerIp, Integer terminal) {
        return createUser("", null, nickname, avtar, registerIp, terminal, false);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public MemberUserDO createUser(String usernameOrMobile, String password, String nickname, String avtar, String registerIp, Integer terminal, Boolean robot) {
        if (StrUtil.isBlank(password)) {
            password = configApi.getConfigValueByKey(ConfigConstants.CONFIG_ACCOUNT_INIT_PASSWORD);
        }
        // 插入用户
        MemberUserDO user = new MemberUserDO();
        user.setNo(serialNoRedisRepository.generateMemberNo());
        if (!PhoneUtil.isMobile(usernameOrMobile)) {
            throw new KnownServiceException("暂不支持“用户名+密码”注册：" + usernameOrMobile);
        }
        user.setMobile(usernameOrMobile);
        user.setStatus(CommonStatusEnum.ENABLE.getStatus()); // 默认开启
        user.setPassword(StrUtil.isBlank(password) ? "" : encodePassword(password)); // 加密密码
        user.setRegisterIp(registerIp).setRegisterTerminal(terminal);
        user.setNickname(nickname).setAvatar(avtar); // 基础信息
        user.setRobot(robot);
        if (StrUtil.isEmpty(nickname)) {
            if (StrUtil.isEmpty(usernameOrMobile)) {
                // 昵称为空时，随机一个名字，避免一些依赖 nickname 的逻辑报错，或者有点丑。例如说，短信发送有昵称时~
                user.setNickname(RandomUtil.randomStringUpper(8));
            } else {
                // 截取手机号码作为昵称
                user.setNickname(RandomUtil.randomStringUpper(4) + StrUtil.subSufByLength(usernameOrMobile, 4));
            }
        }
        memberUserMapper.insert(user);

        // 发送 MQ 消息：用户创建
        TransactionUtils.afterCommit(() -> {
            memberUserProducer.sendUserCreateMessage(user.getId());
            return null;
        });
        return user;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public MemberUserDO createRobot(String nickname, String avtar) {
        return createUser("", null, nickname, avtar, "", TerminalEnum.UNKNOWN.getTerminal(), true);
    }

    @CacheEvict(cacheNames = MEMBER_USER_INFO, key = "#id")
    @Override
    public void updateUserLogin(Long id, String loginIp) {
        MemberUserDO userDO = new MemberUserDO();
        userDO.setId(id);
        userDO.setLoginIp(loginIp);
        userDO.setLoginDate(LocalDateTime.now());
        memberUserMapper.updateById(userDO);
    }

    @Override
    public MemberUserDO getUser(Long id) {
        return memberUserMapper.selectById(id);
    }

    @Override
    public String getUserMobile(Long id) {
        return memberUserMapper.selectMobile(id);
    }

    @Override
    public List<MemberUserDO> getUserList(Collection<Long> ids) {
        if (CollUtil.isEmpty(ids)) {
            return new ArrayList<>();
        }
        ids.removeIf(id -> id == null || id <= 0);
        if (CollUtil.isEmpty(ids)) {
            return new ArrayList<>();
        }
        return memberUserMapper.selectByIds(ids);
    }

    @Override
    public List<MemberUserDO> getUserSimpleList(Collection<Long> ids) {
        if (CollUtil.isEmpty(ids)) {
            return new ArrayList<>();
        }
        ids.removeIf(id -> id == null || id <= 0);
        if (CollUtil.isEmpty(ids)) {
            return new ArrayList<>();
        }
        CoreLambdaQueryWrapper<MemberUserDO> wrapper = new CoreLambdaQueryWrapper<>();
        wrapper.select(
                MemberUserDO::getId,
                MemberUserDO::getPid,
                MemberUserDO::getNo,
                MemberUserDO::getMobile,
                MemberUserDO::getNickname,
                MemberUserDO::getName,
                MemberUserDO::getAvatar,
                MemberUserDO::getSex,
                MemberUserDO::getStatus
        );
        wrapper.in(MemberUserDO::getId, ids);
        return memberUserMapper.selectList(wrapper);
    }

    @Override
    public List<MemberUserDO> getUserList(MemberUserPickReqVO reqVO) {
        return memberUserMapper.selectList(reqVO);
    }

    @CacheEvict(cacheNames = MEMBER_USER_INFO, key = "#userId")
    @Override
    public void updateUser(Long userId, AppMemberUserUpdateReqVO reqVO) {
        MemberUserDO updateObj = BeanUtils.toBean(reqVO, MemberUserDO.class);
        updateObj.setId(userId);
        memberUserMapper.updateById(updateObj);
    }

    @CacheEvict(cacheNames = MEMBER_USER_INFO, key = "#userId")
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateUserMobile(Long userId, AppMemberUserUpdateMobileReqVO reqVO) {
        // 1.1 检测用户是否存在
        MemberUserDO user = validateUserExists(userId);
        // 1.2 校验新手机是否已经被绑定
        validateMobileUnique(null, reqVO.getMobile());

        // 2.1 校验旧手机和旧验证码
        // 补充说明：从安全性来说，老手机也校验 oldCode 验证码会更安全。但是由于 uni-app 商城界面暂时没做，所以这里不强制校验
        if (StrUtil.isNotEmpty(reqVO.getOldCode())) {
            smsCodeApi.useSmsCode(new SmsCodeUseReqDTO().setMobile(user.getMobile()).setCode(reqVO.getOldCode())
                    .setScene(SmsSceneEnum.MEMBER_UPDATE_MOBILE.getScene()).setUsedIp(getClientIP()));
        }
        // 2.2 使用新验证码
        smsCodeApi.useSmsCode(new SmsCodeUseReqDTO().setMobile(reqVO.getMobile()).setCode(reqVO.getCode())
                .setScene(SmsSceneEnum.MEMBER_UPDATE_MOBILE.getScene()).setUsedIp(getClientIP()));

        // 3. 更新用户手机
        MemberUserDO updateObj = new MemberUserDO();
        updateObj.setId(userId);
        updateObj.setMobile(reqVO.getMobile());
        memberUserMapper.updateById(updateObj);
    }

    @CacheEvict(cacheNames = MEMBER_USER_INFO, key = "#userId")
    @Override
    public void updateUserMobileByWeixin(Long userId, AppMemberUserUpdateMobileByWeixinReqVO reqVO) {
        // 1.1 获得对应的手机号信息
        SocialWxPhoneNumberInfoRespDTO phoneNumberInfo = socialClientApi.getWxMaPhoneNumberInfo(
                UserTypeEnum.MEMBER.getValue(), reqVO.getCode());
        Assert.notNull(phoneNumberInfo, "获得手机信息失败，结果为空");
        // 1.2 校验新手机是否已经被绑定
        validateMobileUnique(userId, phoneNumberInfo.getPhoneNumber());

        // 2. 更新用户手机
        MemberUserDO updateObj = new MemberUserDO();
        updateObj.setId(userId);
        updateObj.setMobile(phoneNumberInfo.getPhoneNumber());
        memberUserMapper.updateById(updateObj);
    }

    @Override
    public void updateUserPassword(Long userId, AppMemberUserUpdatePasswordReqVO reqVO) {
        // 检测用户是否存在
        MemberUserDO user = validateUserExists(userId);
        // 校验验证码
        smsCodeApi.useSmsCode(new SmsCodeUseReqDTO().setMobile(user.getMobile()).setCode(reqVO.getCode())
                .setScene(SmsSceneEnum.MEMBER_UPDATE_PASSWORD.getScene()).setUsedIp(getClientIP()));

        // 更新用户密码
        MemberUserDO updateObj = new MemberUserDO();
        updateObj.setId(userId);
        updateObj.setPassword(passwordEncoder.encode(reqVO.getPassword()));
        memberUserMapper.updateById(updateObj);
    }

    @Override
    public void resetUserPassword(AppMemberUserResetPasswordReqVO reqVO) {
        // 检验用户是否存在
        MemberUserDO user = validateUserExists(reqVO.getMobile());

        // 使用验证码
        smsCodeApi.useSmsCode(AuthConvert.INSTANCE.convert(reqVO, SmsSceneEnum.MEMBER_RESET_PASSWORD,
                getClientIP()));

        // 更新密码
        MemberUserDO updateObj = new MemberUserDO();
        updateObj.setId(user.getId());
        updateObj.setPassword(passwordEncoder.encode(reqVO.getPassword()));
        memberUserMapper.updateById(updateObj);
    }

    private MemberUserDO validateUserExists(String mobile) {
        MemberUserDO user = memberUserMapper.selectByMobile(mobile);
        if (user == null) {
            throw exception(USER_MOBILE_NOT_EXISTS);
        }
        return user;
    }

    @Override
    public boolean isPasswordMatch(String rawPassword, String encodedPassword) {
        return passwordEncoder.matches(rawPassword, encodedPassword);
    }

    /**
     * 对密码进行加密
     *
     * @param password 密码
     * @return 加密后的密码
     */
    private String encodePassword(String password) {
        return passwordEncoder.encode(password);
    }

    @CacheEvict(cacheNames = MEMBER_USER_INFO, key = "#reqVO.id")
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateUser(MemberUserUpdateReqVO reqVO) {
        // 校验存在
        validateUserExists(reqVO.getId());
        // 校验手机唯一
        validateMobileUnique(reqVO.getId(), reqVO.getMobile());

        RegionUtils.autoAssignCounty(RegionDataSet.ONLY_LATEST, reqVO.getCountyCode(), reqVO);

        // 更新
        MemberUserDO updateObj = BeanUtils.toBean(reqVO, MemberUserDO.class);
        updateObj.setAreaId(RegionUtils.convertRegionCodeToId(reqVO.getCountyCode()));
        updateObj.setCityId(RegionUtils.convertRegionCodeToId(reqVO.getCityCode()));
        updateObj.setProvinceId(RegionUtils.convertRegionCodeToId(reqVO.getProvinceCode()));
        memberUserMapper.updateById(updateObj);
    }

    @VisibleForTesting
    MemberUserDO validateUserExists(Long id) {
        if (id == null) {
            return null;
        }
        return validateMemberUserExists(id);
    }

    @VisibleForTesting
    void validateMobileUnique(Long id, String mobile) {
        if (StrUtil.isBlank(mobile)) {
            return;
        }
        MemberUserDO user = memberUserMapper.selectByMobile(mobile);
        if (user == null) {
            return;
        }
        // 如果 id 为空，说明不用比较是否为相同 id 的用户
        if (id == null) {
            throw exception(USER_MOBILE_USED, mobile);
        }
        if (!user.getId().equals(id)) {
            throw exception(USER_MOBILE_USED, mobile);
        }
    }

    @Override
    public PageResult<MemberUserDO> getUserPage(MemberUserPageReqVO pageReqVO) {
        return memberUserMapper.selectPage(pageReqVO);
    }

    @CacheEvict(cacheNames = MEMBER_USER_INFO, key = "#id")
    @Override
    public void updateUserLevel(Long id, Long levelId) {
        // 0 代表无等级：防止UpdateById时，会被过滤掉的问题
        levelId = ObjectUtil.defaultIfNull(levelId, 0L);
        MemberUserDO updateObj = new MemberUserDO();
        updateObj.setId(id);
        updateObj.setLevelId(levelId);
        memberUserMapper.updateById(updateObj);
    }

    @Override
    public Long getUserCountByLevelId(Long levelId) {
        return memberUserMapper.selectCountByLevelId(levelId);
    }

    @Override
    public Long getUserCountByTagId(Long tagId) {
        return memberUserMapper.selectCountByTagId(tagId);
    }

    private MemberUserDO validateMemberUserExists(Long id) {
        MemberUserDO userDO = memberUserMapper.selectById(id);
        if (userDO == null) {
            throw exception(USER_NOT_EXISTS);
        }
        return userDO;
    }

    @Override
    public List<MemberUserDO> getRobotList() {
        return memberUserMapper.selectList(new CoreLambdaQueryWrapper<MemberUserDO>()
                .eq(MemberUserDO::getRobot, true));
    }

    @Cacheable(cacheNames = MEMBER_USER_INFO, key = "#id", unless = "#result == null")
    @Nullable
    @Override
    public UserSimpleVO getUserFromCache(Long id) {
        MemberUserDO user = memberUserMapper.selectById(id);
        return BeanUtils.toBean(user, UserSimpleVO.class);
    }

    @Override
    public void updateUserPassword(Long userId, AppMemberUserUpdatePasswordReqVOV2 updateReqVO) {
        // 检测用户是否存在
        MemberUserDO userDO = validateUserExists(userId);
        String oldPassword = passwordEncoder.encode(updateReqVO.getOldPassword());
        if (!passwordEncoder.matches(userDO.getPassword(), oldPassword)) {
            throw exception(USER_OLD_PASSWORD_NOT_MATCH);
        }
        userDO.setPassword(passwordEncoder.encode(updateReqVO.getNewPassword()));
        memberUserMapper.updateById(userDO);
    }

    @CacheEvict(cacheNames = MEMBER_USER_INFO, key = "#id")
    @Override
    public void updateUserAvatar(Long id, String avatar) {
        // 检测用户是否存在
        MemberUserDO userDO = validateUserExists(id);
        // 设置头像
        userDO.setAvatar(avatar);
        memberUserMapper.updateById(userDO);
    }

    @CacheEvict(cacheNames = MEMBER_USER_INFO, key = "#id")
    @Override
    public void updateUserNickname(Long id, String nickname) {
        // 检测用户是否存在
        MemberUserDO userDO = validateUserExists(id);
        // 设置昵称
        userDO.setNickname(nickname);
        memberUserMapper.updateById(userDO);
    }

    @CacheEvict(cacheNames = MEMBER_USER_INFO, key = "#id")
    @Override
    public void updateDeptAndPost(Long id, Long deptId, Set<Long> postIds) {
        MemberUserDO user = validateMemberUserExists(id);
        user.setDeptId(deptId);
        user.setPostIds(postIds.stream().toList());
        memberUserMapper.updateById(user);
    }

    @Override
    public void updateUserRegion(Long id, AppMemberUserUpdateRegionReqVO reqVO) {
        MemberUserDO user = validateMemberUserExists(id);
        int countyId = RegionUtils.convertRegionCodeToId(reqVO.getCountyCode());
        Integer cityId = RegionUtils.getRegionParentId(RegionDataSet.ONLY_LATEST, countyId);
        Integer provinceId = RegionUtils.getRegionParentId(RegionDataSet.ONLY_LATEST, cityId);
        MemberUserDO updateObj = new MemberUserDO();
        updateObj.setId(user.getId());
        updateObj.setProvinceId(provinceId);
        updateObj.setCityId(cityId);
        updateObj.setAreaId(countyId);
        memberUserMapper.updateById(updateObj);
    }

    @Transactional(rollbackFor = Exception.class)
    @CacheEvict(cacheNames = MEMBER_USER_INFO, key = "#id")
    @Override
    public void cancellation(Long id) {
        memberUserMapper.deleteById(id);
        socialUserApiV2.deleteSocialUserByUserId(UserTypeEnum.MEMBER.getValue(), id);
        // 发送 MQ 消息：用户删除
        TransactionUtils.afterCommit(() -> {
            memberUserProducer.sendUserDeleteMessage(id);
            return null;
        });
    }

    @Override
    public Long getUserIdByInviteCode(String inviteCode) {
        // 使用用户编号作为邀请码
        MemberUserDO user = memberUserMapper.selectByNo(inviteCode);
        if (user == null) {
            return null;
        }
        return user.getId();
    }

    @Override
    public boolean bindInviteUser(Long registerUserId, Long inviteUserId) {
        MemberUserDO user = memberUserMapper.selectById(registerUserId);
        if (user == null) {
            log.warn("[bindInviteUser][registerUserId({}) inviteUserId({}) 没有找到新注册的用户]", registerUserId, inviteUserId);
            return false;
        }
        if (user.getPid() != null && user.getPid() > 0) {
            log.warn("[bindInviteUser][registerUserId({}) inviteUserId({}) 新注册的用户已经绑定过推荐人]", registerUserId, inviteUserId);
            return false;
        }
        // 只要未绑定过推荐人都允许随时绑定
        user.setPid(inviteUserId);
        int rows = memberUserMapper.updateById(user);
        return rows > 0;
    }

}
